import { Application } from "egg";
import CommonUtil from "./commonUtil";
import DtoDecorator from "./dtoDecorator";
import MarkdownUtil from "../util/markdownUtil";
import ResultUtil from "../util/resultUtil";
import SwaggerUtil from "../util/swaggerUtil";

namespace ApiDecorator {
    export type IRoute = {
        path: string,
        constructor: any,
        desc: string,
        method: "get" | "post" | "put" | "delete",
        funcName: string,
        in?: Function,
        out?: Function,
        auth: boolean,
        params?: Record<string, { desc: string, name: string, type: DtoDecorator.ApiParamsType, options: DtoDecorator.ApiParamsOptions }>
    };

    export type FuncOptions = {
        in?: Function;
        out?: Function;
        auth?: boolean;
        params?: Record<string, { desc: string, name: string, type: DtoDecorator.ApiParamsType, options: DtoDecorator.ApiParamsOptions }>
    }

    export const routes: IRoute[] = [];

    export const prefix: Record<string, { prefix: string, desc: string, className: string }> = {};

    export const replaceController = (str: string) => {
        return str.replace("Controller", "Apis");
    }

    export function GET(path: string, desc: string, options?: FuncOptions) {
        let newOptions: Required<FuncOptions> = { auth: true, ...(options ?? {}) } as Required<FuncOptions>;
        return function (_target: any, _attr: any) {
            routes.push({
                path,
                constructor: _target.constructor,
                method: "get",
                funcName: _attr,
                desc,
                ...newOptions
            });
        }
    }
    export function POST(path: string, desc: string, options?: FuncOptions) {
        let newOptions: Required<FuncOptions> = { auth: true, ...(options ?? {}) } as Required<FuncOptions>;
        return function (_target: any, _attr: any) {
            routes.push({
                path,
                constructor: _target.constructor,
                method: "post",
                funcName: _attr,
                desc,
                ...newOptions
            });
        }
    }
    export function DELETE(path: string, desc: string, options?: FuncOptions) {
        let newOptions: Required<FuncOptions> = { auth: true, ...(options ?? {}) } as Required<FuncOptions>;
        return function (_target: any, _attr: any) {
            routes.push({
                path,
                constructor: _target.constructor,
                method: "delete",
                funcName: _attr,
                desc,
                ...newOptions
            });
        }
    }
    export function PUT(path: string, desc: string, options?: FuncOptions) {
        let newOptions: Required<FuncOptions> = { auth: true, ...(options ?? {}) } as Required<FuncOptions>;
        return function (_target: any, _attr: any) {
            routes.push({
                path,
                constructor: _target.constructor,
                method: "put",
                funcName: _attr,
                desc,
                ...newOptions
            });
        }
    }

    export function Controller(prefixStr: string, desc: string) {
        return (_class: { name: string }) => {
            prefix[_class.name] = ({ prefix: prefixStr, desc, className: _class.name });
        }
    }

    export function getType(type: DtoDecorator.ApiParamsType) {
        let maps: Record<string, string> = {
            "string": "String",
        }
        let _type = "";
        if (typeof type === "string") {
            _type = maps[type] ?? type;
        }
        else if (type.type === "object") {
            _type = type.prop.name;
        }
        else if (type.type === "list") {
            if (typeof type.prop === "string") {
                _type = `List<${maps[type.type] ?? type.type}>`;
            }
            else {
                _type = `List<${type.prop.name}>`;
            }
        }
        return _type;
    }

    /**生成dart实体 */
    function buildDartEntity(func: Function) {
        let str = "";
        let find = DtoDecorator.params[func.name];
        let filedsList: { name: string, type: string, desc: string }[] = [];
        let objectKeys = Object.keys(find);
        objectKeys.forEach(p => {
            let items = find[p];
            let types = getType(items.type);
            if (typeof items.type === "string") {

            }
            else if (items.type.type === "object") {

                str += buildDartEntity(items.type.prop)
            }
            else if (items.type.type === "list") {
                if (typeof items.type.prop === "string") {

                }
                else {

                    str += buildDartEntity(items.type.prop)
                }
            }
            filedsList.push({ name: p, type: types, desc: items.desc });
        })

        str += `     
@JsonSerializable()
class ${func.name} extends Object {
    ${filedsList.map(p => {
            return `
    /// ${p.desc}
    ${p.type} ${p.name};       
        `
        }).join("")}
    ${func.name}({${filedsList.map(p => `this.${p.name}`).join(",")}});
    factory ${func.name}.fromJson(Map<String, dynamic> srcJson) => _$${func.name}FromJson(srcJson);
    Map<String, dynamic> toJson() => _$${func.name}ToJson(this);
}
        `
        return str;
    }

    /**获取dart代码 */
    function buildDartCode(routes: IRoute[], className: string) {
        let modelsStr = "";
        let funcs = "";
        let modelKeys: string[] = [];
        routes.forEach(p => {
            if (p.in && !modelKeys.includes(p.in.name)) {
                modelKeys.push(p.in?.name);
                modelsStr += buildDartEntity(p.in);
            }
        })
        routes.forEach(p => {
            funcs += `
    /// ${p.desc}
    static Future<void> ${p.funcName}(${p.in ? `${p.in?.name} params` : ""}) {}
            `
        })
        return modelsStr + `
class ${replaceController(className)} {
    ${funcs}
}
        `;
    }

    export async function buildMysqlDB(app: Application) {
        let obj = await app.mysql.query(
            `
        SELECT
            TB.TABLE_SCHEMA,    -- 模式
            TB.TABLE_NAME,      -- 表名
            TB.TABLE_COMMENT,   -- 表名注释
            COL.COLUMN_NAME,    -- 字段名
            COL.COLUMN_TYPE,    -- 字段类型
            COL.COLUMN_COMMENT,  -- 字段注释
                      COL.DATA_TYPE,
                      COL.IS_NULLABLE
        FROM
            INFORMATION_SCHEMA.TABLES TB,
            INFORMATION_SCHEMA.COLUMNS COL
        Where TB.TABLE_SCHEMA = 'follow' -- 数据库名
        AND TB.TABLE_NAME = COL.TABLE_NAME
      `
        ) as any[];
        let str = "namespace IDBModel{";
        let tables: Record<string, any[]> = {};
        obj.forEach(p => {
            !tables[p.TABLE_NAME] && (tables[p.TABLE_NAME] = []);
            tables[p.TABLE_NAME].push(p);
        })
        let tableKeys = Object.keys(tables);
        str += `
            export const tables={
                ${tableKeys.map(p => `${p}:'${p}'`).join(",\r\n")}
            }
        `
        for (const key in tables) {
            const element = tables[key];
            str += `\r\n  export interface ${CommonUtil.replaceUnderLine(key.replace("tb_", ""), "_", true)}{`;
            element.forEach(p => {
                str += `
        /**${p.COLUMN_COMMENT} */
        ${CommonUtil.replaceUnderLine(p.COLUMN_NAME)}${p.IS_NULLABLE === "NO" ? "" : "?"}:${CommonUtil.typeMapping(p.DATA_TYPE)};
        `
            })
            str += "}";
        }
        str += "\r\n}\r\n\r\nexport default IDBModel";
        return str;
    }

    export function BindRoute(app: Application) {
        new SwaggerUtil().genSwaggerDocs();
        routes.forEach(p => {
            let controller = prefix[p.constructor.name];
            let path = "/api" + controller.prefix + p.path;
            app.router[p.method](path, async (ctx) => {
                // 判断登录
                if (p.auth) {
                    let _token = ctx.request.headers["token"] as string;
                    if (!_token || !app.jwt.verify(_token, ctx.app.config.jwt.secret)) {
                        ctx.status = 401;
                        return;
                    }
                }
                try {
                    let datas = CommonUtil.getRequestData(ctx);
                    if (p.in) {
                        let params = DtoDecorator.params[p.in.name];
                        for (const key in params) {
                            const element = params[key];
                            let items = datas[key];
                            if (element.options.rule && ![undefined, null].includes(items)) {
                                if (Array.isArray(element.options.rule)) {
                                    for (const key in element.options.rule) {
                                        const _element = element.options.rule[key];
                                        if (!new RegExp(_element.regexp).test(items.toString())) {
                                            ctx.body = ResultUtil.errorMsg(_element.errorMsg);
                                            return;
                                        }
                                    }
                                }
                                else {
                                    if (!new RegExp(element.options.rule.regexp).test(items.toString())) {
                                        ctx.body = ResultUtil.errorMsg(element.options.rule.errorMsg);
                                        return;
                                    }
                                }
                            }
                            if (element.options.required && !datas[key]) {
                                ctx.body = ResultUtil.errorMsg("缺少必传参数");
                                return;
                            }
                        }
                    }
                    await new p.constructor(ctx)[p.funcName](datas);
                } catch (error) {
                    ctx.body = ResultUtil.errorMsg((error as any).message);
                }
            })

            app.router.get(path + "/code/ts", (ctx) => {
                MarkdownUtil.buildReactApiCode(p, ctx);
            })

            app.router.get("/util/code/dart", (ctx) => {
                let models = "";
                Object.keys(prefix).forEach(p => {
                    const _filter = routes.filter(sp => sp.constructor.name === p);
                    models += buildDartCode(_filter, p);
                })
                ctx.body = models;
            })

            /**mysql 转成实体 */
            app.router.get("/util/code/mysql", async (ctx) => {
                ctx.body = await buildMysqlDB(app);
            })

        })
    }

}

export default ApiDecorator;